package com.sky.aspect;

import com.sky.annotation.PreventDuplicate;
import com.sky.context.BaseContext;
import com.sky.exception.BaseException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * PreventDuplicateAspect : 防止重复提交切面
 *
 * @author cdz
 * @create 2025-03-03  15:50
 */

@Aspect
@Component
public class PreventDuplicateAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     *  获取方法参数名
     */
    private static final ParameterNameDiscoverer PARAM_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    /**
     * 获取方法参数名
     * @param method
     * @return
     */
    private String[] getParameterNames(Method method) {
        return PARAM_NAME_DISCOVERER.getParameterNames(method);
    }

    @Around("@annotation(prevent)")
    public Object checkDuplicate(ProceedingJoinPoint joinPoint, PreventDuplicate prevent) throws Throwable {
        // 1. 解析SpEL表达式生成唯一Key
        String uniqueKey = generateUniqueKey(joinPoint, prevent.key());
        String lockKey = "prevent:submit:" + uniqueKey;

        // 2. 尝试获取分布式锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(lockKey, "1", prevent.expire(), TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(success)) {
            throw new BaseException(prevent.message());
        }

        try {
            // 3. 执行业务逻辑
            return joinPoint.proceed();
        } finally {
            // 4. 业务完成后删除Key（根据业务需求决定是否立即释放）
             stringRedisTemplate.delete(lockKey);
        }
    }


    /**
     * 解析SpEL表达式生成动态Key
     * @param joinPoint
     * @param keyExpression
     * @return
     */
    private String generateUniqueKey(ProceedingJoinPoint joinPoint, String keyExpression) {
        // 1. 如果表达式为空，默认生成类+方法+参数哈希的Key（确保基本唯一性）
        if (keyExpression == null || keyExpression.isEmpty()) {
            return defaultKey(joinPoint);
        }

        // 2. 获取方法签名和参数值
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = getParameterNames(signature.getMethod());

        // 3. 创建SpEL解析上下文，绑定参数名和值
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }

        // 注入缓存中的用户Id
        Long userId = BaseContext.getCurrentId();
        // 绑定到上下文变量
        context.setVariable("userId", userId);

        // 4. 解析表达式
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyExpression);
        Object value = expression.getValue(context);

        // 5. 确保解析结果非空
        if (value == null) {
            throw new IllegalArgumentException("SpEL表达式解析结果为空: " + keyExpression);
        }

        // 6. 组合类名+方法名+表达式值生成唯一Key（避免不同接口冲突）
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getMethod().getName();
        return String.format("lock:%s:%s:%s", className, methodName, value);
    }

    /**
     *  默认Key生成策略：类名+方法名+参数哈希
     * @param joinPoint
     * @return
     */
    private String defaultKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getMethod().getName();
        int paramsHash = Objects.hash(joinPoint.getArgs());
        return String.format("lock:%s:%s:%d", className, methodName, paramsHash);
    }
}
